package com.summer.data.dialect.postgre;

import com.summer.data.Jsql;
import com.summer.data.annotation.Comment;
import com.summer.data.annotation.column.*;
import com.summer.data.dialect.AbstractDialect;
import com.summer.data.logging.Logger;
import com.summer.data.logging.LoggerFactory;
import com.summer.data.parameter.BlobParameter;
import com.summer.data.parameter.ClobParameter;
import com.summer.data.rs.extractor.KeysExtractor;
import com.summer.data.table.BaseColumn;
import com.summer.data.table.BaseTable;
import com.summer.data.table.NoTableNameException;
import com.summer.data.annotation.column.Default;
import com.summer.data.table.column.*;
import com.summer.data.type.Tuple2;
import com.summer.data.util.TableUtils;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.JDBCType;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;

/**
 * @author ydfqy.com
 *  postgre数据库方言
 */
public class PostgreSqlDialect extends AbstractDialect {

    private static final Logger logger = LoggerFactory.getLogger(PostgreSqlDialect.class);

    private static final Map<Class<?>, JDBCType> JAVA_2_JDBC = new HashMap<Class<?>, JDBCType>(){{
        put(Boolean.class, JDBCType.BOOLEAN);
        put(Byte.class, JDBCType.BIT);

        put(Short.class, JDBCType.SMALLINT);
        put(Integer.class, JDBCType.INTEGER);
        put(Long.class, JDBCType.BIGINT);

        put(Float.class, JDBCType.FLOAT);
        put(Double.class, JDBCType.DOUBLE);

        put(BigDecimal.class, JDBCType.DECIMAL);

        put(String.class, JDBCType.VARCHAR);

        // 日期是最复杂的
        put(LocalDate.class, JDBCType.DATE);
        put(LocalTime.class, JDBCType.TIME_WITH_TIMEZONE);
        put(LocalDateTime.class, JDBCType.TIME);

        put(Date.class, JDBCType.DATE);
        put(java.sql.Date.class, JDBCType.DATE);

        put(Timestamp.class, JDBCType.TIMESTAMP);

        put(ClobParameter.class, JDBCType.CLOB);
        put(BlobParameter.class, JDBCType.BLOB);

    }};

    @Override
    public Map<Class<?>, JDBCType> getJDBCTypeMap() {
        return JAVA_2_JDBC;
    }

    @Override
    public String getPageSql(String string, Integer offset, Integer pageSize) {
        if (offset == 0) {
            return String.format("%s LIMIT %d ", string, pageSize);
        } else {
            return String.format("%s LIMIT %d OFFSET %d ", string, pageSize, offset);
        }
    }

    @Override
    public <T extends BaseTable> List<Jsql> buildCreateTableSql(Class<T> clazz) {

        // 表名
        String tableName = TableUtils.getTableName(clazz);
        if (tableName == null || tableName.isEmpty()) {
            throw new NoTableNameException(clazz);
        }

        List<Jsql> result = new ArrayList<>();

        // 主键列表
        List<String> primaryKeyList = new ArrayList<>();
        // 唯一索引
//        Map<String, List<String>> unionIndexMap = new HashMap<>();
        // 索引
        Map<String, List<String>> indexMap = new HashMap<>();

        // 用于建表语句生成
        StringBuffer buffer = new StringBuffer();
        Formatter formatter = new Formatter(buffer);
        formatter.format("CREATE TABLE %s (", tableName);

        Field[] fields = clazz.getFields();
        // 是否有列定义
        boolean hasColumn = false;
        for (Field field : fields) {
            Class<?> fieldType = field.getType();
            if (BaseColumn.class.isAssignableFrom(fieldType)) {
                if (hasColumn) {
                    buffer.append(",");
                }
                hasColumn = true;

                buffer.append(System.lineSeparator());

                String columnName = AbstractDialect.getColumnName(field);

                // BoolColumn -> bool
                if (BoolColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "bool");
                } else if (IntegerColumn.class.isAssignableFrom(fieldType)) {
                    AutoGenerated autoGenerated = field.getAnnotation(AutoGenerated.class);
                    if (autoGenerated == null) {
                        formatter.format(" %s %s", columnName, "int4");
                    } else {
                        formatter.format(" %s %s", columnName, "serial");
                    }
                } else if (FloatColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "float4");
                } else if (DoubleColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "float8");
                } else if (DecimalColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "decimal");
                } else if (DateColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "date");
                } else if (TimeColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "time(6)");
                } else if (DateTimeColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "timestamp(6)");
                } else if (TimestampColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "timestamp(6)");
                } else if (StringColumn.class.isAssignableFrom(fieldType)) {
                    MaxLength maxLengthAnnotation = field.getAnnotation(MaxLength.class);
                    int maxLength = 255;
                    if (maxLengthAnnotation != null) {
                        maxLength = maxLengthAnnotation.value();
                    }
                    formatter.format(" %s %s(%d)", columnName, "varchar", maxLength);
                } else if (ClobColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "text");
                } else if (BlobColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "bytea");
                } else {
                    logger.warn("发现未处理的字段类型：%s", field.getType().getName());
                }

                // COLLATE
                if (StringColumn.class.isAssignableFrom(fieldType)) {
                    Collate[] collates = field.getAnnotationsByType(Collate.class);

                    Tuple2<Collate> tuple2 = TableUtils.findBestCollate(collates, this.getClass());
                    if (tuple2.t1 != null) {
                        // 找到了
                        formatter.format(" %s ", tuple2.t1.value());

                    } else if (tuple2.t2 != null) {
                        // 未找到，用默认的
                        formatter.format(" %s ", tuple2.t2.value());
                    }
                }

                // Not Null 处理
                NotNull notNullAnnotation = field.getAnnotation(NotNull.class);
                if (notNullAnnotation != null) {
                    formatter.format(" NOT NULL ");
                }

                // default 处理
                Default defalutAnnotation = field.getAnnotation(Default.class);
                if (defalutAnnotation != null) {
                    formatter.format(" %s ", defalutAnnotation.value());
                }

                // PrimaryKey
                PrimaryKey primaryKeyAnnotation = field.getAnnotation(PrimaryKey.class);
                if (primaryKeyAnnotation != null) {
                    primaryKeyList.add(columnName);
                }

                // 收集唯一索引

                // 收集索引
                Index indexAnnotation = field.getAnnotation(Index.class);
                if (indexAnnotation != null) {
                    String indexName = indexAnnotation.value();
                    if (!indexMap.containsKey(indexName)) {
                        indexMap.put(indexName, new ArrayList<>());
                    }
                    indexMap.get(indexName).add(columnName);
                }

                // 收集HashIndex
                HashIndex hashIndexAnnotation = field.getAnnotation(HashIndex.class);
                if (hashIndexAnnotation != null) {
                    String indexName = hashIndexAnnotation.value();
                    if (indexName.isEmpty()) {
                        indexName = columnName;
                    }
                    result.add(
                            Jsql.format("CREATE INDEX idxh_%s ON %s USING hash (%s);",
                                    indexName, tableName, columnName
                            )
                    );
                }

                // 行注释
                Comment commentAnnotation = field.getAnnotation(Comment.class);
                if (commentAnnotation != null) {
                    result.add(Jsql.format("COMMENT ON COLUMN %s.%s IS '%s';",
                            tableName,
                            columnName,
                            commentAnnotation.value()
                    ));
                }
            }
        }

        // PrimaryKey
        if (!primaryKeyList.isEmpty()) {
            formatter.format(",%sCONSTRAINT PK_%s PRIMARY KEY (%s)",
                    System.lineSeparator(),
                    tableName,
                    String.join(",", primaryKeyList));
        }

        // 收口
        formatter.format("%s);", System.lineSeparator());

        // 生成创建索引的 Jsql
        for (Map.Entry<String, List<String>> indexEntry : indexMap.entrySet()) {
            String idxName = indexEntry.getKey();

            if (indexEntry.getValue().isEmpty()) {
                continue;
            }
            result.add(Jsql.format("CREATE INDEX IDX_%s ON %s (%s);",
                    idxName,
                    tableName,
                    String.join(",", indexEntry.getValue())
            ));
        }

        // 表注释
        Comment commentAnnotation = clazz.getAnnotation(Comment.class);
        if (commentAnnotation != null) {
            result.add(Jsql.format("COMMENT ON TABLE %s IS '%s';",
                    tableName,
                    commentAnnotation.value()
            ));
        }

        // 加上建表语句
        result.add(0, new Jsql(buffer.toString()));
        return result;

    }

    @Override
    public KeysExtractor buildKeysExtractor(BaseTable table) {
        // todo:
        return new KeysExtractor();
    }

//    @Override
//    public boolean isSupportUpsert() {
//        // 注意，ON CONFLICT 只在 PostgreSQL 9.5 以上可用。
//        return true;
//    }
//
//    @Override
//    public <T extends BaseTable> Jsql buildUpsertSql(T t, FilterAble... exps) {
//
//        assert exps.length > 0;
//
//        StringBuffer colBuffer = new StringBuffer();
//        StringBuffer valBuffer = new StringBuffer();
//        List<ParameterStruct> parameterStructList = new ArrayList<ParameterStruct>();
//
//        try {
//            Class<? extends BaseTable> clazz = t.getClass();
//            Field[] fields = clazz.getFields();
//            boolean isFirst = true;
//            for (Field field : fields) {
//                // 自动生成的字段不用处理
//                AutoGenerated autoGenerated = field.getAnnotation(AutoGenerated.class);
//                Object fieldObj = field.get(t);
//
//                // IntegerColumn + AutoGenerated 表明是由数据库自动生成，无需在 java 端处理
//                if (fieldObj instanceof IntegerColumn && autoGenerated != null) {
//                    continue;
//                }
//
//                if (fieldObj instanceof BaseColumn) {
//                    BaseColumn<?> column = (BaseColumn<?>) fieldObj;
//
//                    // StringColumn + AutoGenerated + value 未设定值， 表明咋 java 端由 UUID 生成
//                    if (fieldObj instanceof StringColumn && autoGenerated != null && column.value == null) {
//                        StringColumn stringColumn = (StringColumn) fieldObj;
//
//                        String uuidString = UUID.randomUUID().toString();
//                        stringColumn.setValue(uuidString);
//                    }
//
//                    if (column.isNeedInsert()) {
//                        if (!isFirst) {
//                            colBuffer.append(",");
//                            valBuffer.append(",");
//                        }
//                        isFirst = false;
//                        colBuffer.append(column.colName);
//                        valBuffer.append("?");
//                        parameterStructList.add(new ParameterStruct(column.value));
//                    }
//                }
//            }
//
//            Jsql jsql = Jsql.format("INSERT INTO %s (%s) VALUES (%s)", t.getTableName(), colBuffer, valBuffer);
//            jsql.appendParameters(parameterStructList.toArray(new ParameterStruct[0]));
//
//            jsql.append(" ON CONFLICT ");
//            Jsql whereSql = Dialect.buildWhereSql(this, exps);
//            jsql.append(" ON CONFLICT ");
//
//            if (Logger.isLogSql()) {
//                jsql.print(this);
//            }
//
//            return jsql;
//
//        } catch (IllegalArgumentException | IllegalAccessException e) {
//            throw new RuntimeException(e);
//        }
//    }

}
